package besta.moon.statement;

import java.sql.*;
import java.util.ArrayList;

import besta.moon.sql.MoonParser;
import besta.moon.MoonException;
import besta.moon.MoonConnection;
import besta.moon.MoonCode;
import besta.moon.MoonCommand;

public class MoonStatement implements Statement {

    public final MoonConnection con;

    MoonCommand cmd;

    private boolean isClosed;

    public int rsType;

    public int rsConcurrency;

    private int fetchDirection;

    private int fetchSize;

    private int queryTimeout;

    private int maxRows;

    private int maxFieldSize;

    private ArrayList batches;

    private boolean needGeneratedKeys;

    private ResultSet generatedKeys;

    private int[] generatedKeyIndexes;

    private String[] generatedKeyNames;

    public MoonStatement(MoonConnection con) throws SQLException {
        this(con, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    }

    public MoonStatement(MoonConnection con, int rsType, int rsConcurrency)
            throws SQLException {
        this.con = con;
        this.rsType = rsType;
        this.rsConcurrency = rsConcurrency;
        con.testClosedConnection();
    }

    @Override
    final public ResultSet executeQuery(String sql) throws SQLException {
        executeImpl(sql);
        return cmd.getQueryResult();
    }

    @Override
    final public int executeUpdate(String sql) throws SQLException {
        executeImpl(sql);
        return cmd.getUpdateCount();
    }

    @Override
    final public boolean execute(String sql) throws SQLException {
        executeImpl(sql);
        return cmd.getResultSet() != null;
    }

    private void executeImpl(String sql) throws SQLException {
        checkStatement();
        generatedKeys = null;
        try {
            con.log.println(sql);
            MoonParser parser = new MoonParser();
            cmd = parser.parse(con, sql);
            if (maxRows != 0
                    && (cmd.getMaxRows() == -1 || cmd.getMaxRows() > maxRows)) {
                cmd.setMaxRows(maxRows);
            }
            cmd.execute(con, this);
        } catch (SQLException e) {
            throw MoonException.createFromException(e);
        }
        needGeneratedKeys = false;
        generatedKeyIndexes = null;
        generatedKeyNames = null;
    }

    @Override
    final public void close() {
        con.log.println("Statement.close");
        isClosed = true;
        cmd = null;
    }

    @Override
    final public int getMaxFieldSize() {
        return maxFieldSize;
    }

    @Override
    final public void setMaxFieldSize(int max) {
        maxFieldSize = max;
    }

    @Override
    final public int getMaxRows() {
        return maxRows;
    }

    @Override
    final public void setMaxRows(int max) throws SQLException {
        if (max < 0) {
            throw MoonException.create(MoonCode.ROWS_WRONG_MAX,
                    String.valueOf(max));
        }
        maxRows = max;
    }

    @Override
    final public void setEscapeProcessing(boolean enable) throws SQLException {
        checkStatement();
    }

    @Override
    final public int getQueryTimeout() throws SQLException {
        checkStatement();
        return queryTimeout;
    }

    @Override
    final public void setQueryTimeout(int seconds) throws SQLException {
        checkStatement();
        queryTimeout = seconds;
    }

    @Override
    final public void cancel() throws SQLException {
        checkStatement();
    }

    @Override
    final public SQLWarning getWarnings() {
        return null;
    }

    @Override
    final public void clearWarnings() {
       
    }

    @Override
    final public void setCursorName(String name) throws SQLException {

        throw MoonException.create(MoonCode.UNSUPPORTED_OPERATION,
                "setCursorName");
    }

    @Override
    final public ResultSet getResultSet() throws SQLException {
        checkStatement();
        return cmd.getResultSet();
    }

    @Override
    final public int getUpdateCount() throws SQLException {
        checkStatement();
        return cmd.getUpdateCount();
    }

    @Override
    final public boolean getMoreResults() throws SQLException {
        checkStatement();
        return getMoreResults(CLOSE_CURRENT_RESULT);
    }

    @Override
    final public void setFetchDirection(int direction) throws SQLException {
        checkStatement();
        fetchDirection = direction;
    }

    @Override
    final public int getFetchDirection() throws SQLException {
        checkStatement();
        return fetchDirection;
    }

    @Override
    final public void setFetchSize(int rows) throws SQLException {
        checkStatement();
        fetchSize = rows;
    }

    @Override
    final public int getFetchSize() throws SQLException {
        checkStatement();
        return fetchSize;
    }

    @Override
    final public int getResultSetConcurrency() throws SQLException {
        checkStatement();
        return rsConcurrency;
    }

    @Override
    final public int getResultSetType() throws SQLException {
        checkStatement();
        return rsType;
    }

    @Override
    final public void addBatch(String sql) {
        if (batches == null) {
            batches = new ArrayList();
        }
        batches.add(sql);
    }

    @Override
    public void clearBatch() throws SQLException {
        checkStatement();
        if (batches == null) {
            return;
        }
        batches.clear();
    }

    @Override
    public int[] executeBatch() throws BatchUpdateException {
        if (batches == null) {
            return new int[0];
        }
        final int[] result = new int[batches.size()];
        BatchUpdateException failed = null;
        for (int i = 0; i < result.length; i++) {
            try {
                result[i] = executeUpdate((String) batches.get(i));
            } catch (SQLException ex) {
                result[i] = EXECUTE_FAILED;
                if (failed == null) {
                    failed = new BatchUpdateException(ex.getMessage(),
                            ex.getSQLState(), ex.getErrorCode(), result);
                    failed.initCause(ex);
                }
                failed.setNextException(ex);
            }
        }
        batches.clear();
        if (failed != null) {
            throw failed;
        }
        return result;
    }

    @Override
    final public Connection getConnection() {
        return con;
    }

    @Override
    final public boolean getMoreResults(int current) throws SQLException {
        switch (current) {
            case CLOSE_ALL_RESULTS:
            case CLOSE_CURRENT_RESULT:
                ResultSet rs = cmd.getResultSet();
                cmd.rs = null;
                if (rs != null) {
                    rs.close();
                }
                break;
            case KEEP_CURRENT_RESULT:
                break;
            default:
                throw MoonException.create(MoonCode.FLAGVALUE_INVALID,
                        String.valueOf(current));
        }
        return cmd.getMoreResults();
    }

    public final void setNeedGeneratedKeys(int autoGeneratedKeys) throws SQLException {
        switch (autoGeneratedKeys) {
            case NO_GENERATED_KEYS:
                break;
            case RETURN_GENERATED_KEYS:
                needGeneratedKeys = true;
                break;
            default:
                throw MoonException.create(MoonCode.ARGUMENT_INVALID,
                        String.valueOf(autoGeneratedKeys));
        }
    }

    public final void setNeedGeneratedKeys(int[] columnIndexes) throws SQLException {
        needGeneratedKeys = columnIndexes != null;
        generatedKeyIndexes = columnIndexes;
    }

    public final void setNeedGeneratedKeys(String[] columnNames) throws SQLException {
        needGeneratedKeys = columnNames != null;
        generatedKeyNames = columnNames;
    }

    public final boolean needGeneratedKeys() {
        return needGeneratedKeys;
    }

    public final int[] getGeneratedKeyIndexes() {
        return generatedKeyIndexes;
    }

    public final String[] getGeneratedKeyNames() {
        return generatedKeyNames;
    }

    public final void setGeneratedKeys(ResultSet rs) {
        generatedKeys = rs;
    }

    @Override
    final public ResultSet getGeneratedKeys() throws SQLException {
        if (generatedKeys == null) {
            throw MoonException.create(MoonCode.GENER_KEYS_UNREQUIRED);
        }
        return generatedKeys;
    }

    @Override
    final public int executeUpdate(String sql, int autoGeneratedKeys)
            throws SQLException {
        setNeedGeneratedKeys(autoGeneratedKeys);
        return executeUpdate(sql);
    }

    @Override
    final public int executeUpdate(String sql, int[] columnIndexes)
            throws SQLException {
        setNeedGeneratedKeys(columnIndexes);
        return executeUpdate(sql);
    }

    @Override
    final public int executeUpdate(String sql, String[] columnNames)
            throws SQLException {
        setNeedGeneratedKeys(columnNames);
        return executeUpdate(sql);
    }

    @Override
    final public boolean execute(String sql, int autoGeneratedKeys)
            throws SQLException {
        setNeedGeneratedKeys(autoGeneratedKeys);
        return execute(sql);
    }

    @Override
    final public boolean execute(String sql, int[] columnIndexes)
            throws SQLException {
        setNeedGeneratedKeys(columnIndexes);
        return execute(sql);
    }

    @Override
    final public boolean execute(String sql, String[] columnNames)
            throws SQLException {
        setNeedGeneratedKeys(columnNames);
        return execute(sql);
    }

    @Override
    final public int getResultSetHoldability() throws SQLException {
        throw new java.lang.UnsupportedOperationException(
                "Method getResultSetHoldability() not yet implemented.");
    }

    void checkStatement() throws SQLException {
        if (isClosed) {
            throw MoonException.create(MoonCode.STMT_IS_CLOSED);
        }
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public boolean isClosed() throws SQLException {
        return false;
    }

    @Override
    public void setPoolable(boolean poolable) throws SQLException {

    }

    @Override
    public boolean isPoolable() throws SQLException {
        return false;
    }

    @Override
    public void closeOnCompletion() throws SQLException {

    }

    @Override
    public boolean isCloseOnCompletion() throws SQLException {
        return false;
    }
}
